#!/usr/bin/python
import time, os, select, signal, sys,syslog

#default constants
VERSION="1.0beta"
FORCED_KILL_EXIT_CODE=-1
TIME_LIMIT=300
MAX_RESTART_IN_TIME_FRAME=10
EXIT_CODE=0
VERBOSE=(1==0)
LOG=(1==1)
LOGSIGNALS=(1==1)

def help():
	print("")
	print("TaskLauncher application v." +VERSION)
	print("Usage: launcher  [--option parameter \;]* [--exec program [parameters] \;]")
	print("")
	print("Options:")
	print("--exec command [parameters] \;")
	print("--trigger command [parameters] \;")
	print("--log 0/1 \;")
	print("--log-signals 0/1 \;")
	print("--forced-kill-exit-code NUM \;")
	print("--exit-code NUM \;")
	print("--time-limit NUM \;")
	print("--restart-limit NUM\;")
	print("--verbose 0/1 \;")
	print("")

#extract the parameter of the command line option
def extract_option(option):
	result=[]
	has_option=sys.argv.count("--"+option)
	if has_option==1:
		result.extend(sys.argv)
		index=result.index("--"+option)
		del result[:index+1]
		index=result.index(";")
		del result[index:]
	return result

#has option?
def has_option(option):
	return sys.argv.count("--"+option)>0

#signal handler
def handler(signum, frame):
	if LOGSIGNALS:
		if parentpid==os.getpid():
			syslog.syslog("TaskLauncher application: parent ("+str(parentpid)+")  got signal: "+str(signum))
		else:
			syslog.syslog("TaskLauncher application: child  ("+str(os.getpid())+") got signal: "+str(signum))    
	if childpid!=0:
		try:
			if LOGSIGNALS:
				syslog.syslog("TaskLauncher application: send signal ("+str(signum)+") to: "+str(childpid))    
			os.kill(childpid,signum)
		except OSError as e:
			pass
		if signum==15:
			pass

#----------------- Parsing command line options  ----------------------
# command to execute
arguments=extract_option("exec")
# trigger command
trigger=extract_option("trigger")

# forced exit code 
constant=extract_option("forced-kill-exit-code")
if len(constant)==1:
    FORCED_KILL_EXIT_CODE=int(constant[0])

# min time difference
constant=extract_option("time-limit")
if len(constant)==1:
    TIME_LIMIT=int(constant[0])

# min time difference
constant=extract_option("restart-limit")
if len(constant)==1:
    MAX_RESTART_IN_TIME_FRAME=int(constant[0])

# min time difference
constant=extract_option("exit-code")
if len(constant)==1:
    EXIT_CODE=int(constant[0])

# verbose
constant=extract_option("verbose")
if len(constant)==1:
    VERBOSE=int(constant[0])!=0

# log
constant=extract_option("log")
if len(constant)==1:
    LOG=int(constant[0])!=0

# log signals
constant=extract_option("log-signals")
if len(constant)==1:
    LOGSIGNALS=int(constant[0])!=0


#----------------- End of parsing command line options  ----------------------

if has_option("help") or len(sys.argv)<2 or not has_option("exec"):
	help()
	sys.exit(1)

if VERBOSE:
    print("")
    print("TaskLauncher application v." +VERSION)
    print("")
    print("Parameters:")
    print("--exec             : "+str(arguments))
    print("--trigger          : "+str(trigger))
    print("--forced-exit-code : "+str(FORCED_KILL_EXIT_CODE))
    print("--exit-code        : "+str(EXIT_CODE))
    print("--time-limit       : "+str(TIME_LIMIT))
    print("--restart-limit    : "+str(MAX_RESTART_IN_TIME_FRAME))
    print("--log              : "+str(LOG))
    print("--log-signals      : "+str(LOGSIGNALS))
    print("--verbose          : "+str(VERBOSE))
    print("")
if has_option("help") or len(sys.argv)<2 or not has_option("exec"):
	help()
	sys.exit(1)


# Main 
parentpid=os.getpid()
if LOG:
    syslog.syslog("TaskLauncher application: ("+str(parentpid)+") arguments: "+str(sys.argv))

# register signal handlers
i=1
while(i<30):
	if (i!=9 and i!=19):
		signal.signal(i, handler)
	i=i+1

# fork a child
childpid= os.fork()

# create new process group, become its leader
if childpid==0:
	os.setpgrp()
	restart_counter=0
	firststart_time=time.time()
	#Main loop
	while (restart_counter<MAX_RESTART_IN_TIME_FRAME ):
#	Start a Child
	    if firststart_time+TIME_LIMIT>time.time():
		    restart_counter+=1
	    else:
		    firststart_time=time.time()
		    restart_counter=0
	    if LOG:
		    syslog.syslog("TaskLauncher application: ("+str(parentpid)+") : start ")
	    try:
		    if VERBOSE:
			    print("TaskLauncher application: ("+str(parentpid)+") : start :"+str(arguments))
		    childpid=os.spawnvp(os.P_NOWAIT, arguments[0],arguments)
		
	    except OSError as e:
		    pass
	    child_status=os.waitpid(childpid,os.WNOHANG)
	    while (child_status==(0,0)):
		    time.sleep(1)
		    if not(os.path.exists("/proc/"+str(parentpid))):
			    if LOG:
				    syslog.syslog("TaskLauncher application: ("+str(parentpid)+") : Parent died -> forced kill")
			    os.killpg(os.getpgid(childpid),15)
			    os.killpg(os.getpgid(childpid),9)
			    sys.exit(FORCED_KILL_EXIT_CODE)
		    child_status=os.waitpid(childpid,os.WNOHANG)
	    st=child_status[1]
	    # Check child exit code
	    if st>>8 == EXIT_CODE:
		    if LOG:
			    syslog.syslog("TaskLauncher application: ("+str(parentpid)+") : Exit with status:"+str(st>>8)+" (==exit-code "+str(EXIT_CODE)+")")
		    sys.exit(st>>8)
	    else:
		    if len(trigger)>0:
			    if LOG:
				    syslog.syslog("TaskLauncher application: ("+str(parentpid)+") : exit with status:"+str(st>>8)+" (!=exit-code "+str(EXIT_CODE)+")")
				    syslog.syslog("TaskLauncher application: ("+str(parentpid)+") : trigger: "+str(trigger))
			    if VERBOSE:
				    print("TaskLauncher application: ("+str(parentpid)+") : trigger :"+str(trigger))
			    try:
				    os.spawnvp(os.P_WAIT, trigger[0],trigger)
			    except OSError as e:
				    pass

	# Too many restart
	if LOG:
	    syslog.syslog("TaskLauncher application: ("+str(parentpid)+") : too many restart -> forced kill")
	try:
	    os.killpg(os.getpgid(childpid),15)
	    os.killpg(os.getpgid(childpid),9)
	except OSError as e:
	    pass
	sys.exit(FORCED_KILL_EXIT_CODE)
else:
	child_status=os.waitpid(childpid,os.WNOHANG)
	while (child_status==(0,0)):
	    child_status=os.waitpid(childpid,os.WNOHANG)
	    time.sleep(1)
	st=child_status[1]
	sys.exit(st>>8)
